# 内核态与用户态转换的桥梁
#include "../include/memlayout.h"
#include "../include/riscv.h"

.section trampsec
.global trampoline
trampoline:
.align 4                     # 因为rv64指令是32位的，所以4字节对齐 

# 执行用户程序之前，将stvec寄存器设置为该函数地址
# 用户程序陷入发生时，将自动跳转到该函数执行
.global uservec
uservec:
        # 将a0的值暂时保存到sscratch寄存器，以便用a0来做转换
        csrw sscratch, a0 
        # 将TRAPFRAME的虚拟地址存入a0，由于已经开启页表，因此会自动将虚拟地址转换为物理地址
        # 与进程的trapframe的实际物理地址映射
        li a0, TRAPFRAME
        # 将用户寄存器保存在TRAPFRAME（p->trapframe）中
        # 其相对位置与trapframe结构体中的定义有关
        sd ra, 40(a0)    # 返回地址寄存器(Return Address)，保存函数调用的返回地址
        sd sp, 48(a0)    # 栈指针寄存器(Stack Pointer)，指向当前栈帧的底部
        sd gp, 56(a0)    # 全局指针(Global Pointer)，用于访问静态/全局数据
        sd tp, 64(a0)    # 线程指针(Thread Pointer)，用于线程本地存储
        sd t0, 72(a0)    # 临时寄存器0，用于临时计算和数据传递
        sd t1, 80(a0)    # 临时寄存器1
        sd t2, 88(a0)    # 临时寄存器2
        sd s0, 96(a0)    # 保存寄存器0，也称为帧指针(Frame Pointer)
        sd s1, 104(a0)   # 保存寄存器1，函数中需要保存的值

        # 将临时保存在sscratch中的a0取出来保存到正确的位置
        csrr t0, sscratch
        sd t0, 112(a0)   

        sd a1, 120(a0)   # 参数寄存器1，用于函数参数传递
        sd a2, 128(a0)   # 参数寄存器2
        sd a3, 136(a0)   # 参数寄存器3
        sd a4, 144(a0)   # 参数寄存器4
        sd a5, 152(a0)   # 参数寄存器5
        sd a6, 160(a0)   # 参数寄存器6
        sd a7, 168(a0)   # 参数寄存器7，在系统调用中也用作系统调用号
        sd s2, 176(a0)   # 保存寄存器2，函数中需要保存的值
        sd s3, 184(a0)   # 保存寄存器3
        sd s4, 192(a0)   # 保存寄存器4
        sd s5, 200(a0)   # 保存寄存器5
        sd s6, 208(a0)   # 保存寄存器6
        sd s7, 216(a0)   # 保存寄存器7
        sd s8, 224(a0)   # 保存寄存器8
        sd s9, 232(a0)   # 保存寄存器9
        sd s10, 240(a0)  # 保存寄存器10
        sd s11, 248(a0)  # 保存寄存器11
        sd t3, 256(a0)   # 临时寄存器3
        sd t4, 264(a0)   # 临时寄存器4
        sd t5, 272(a0)   # 临时寄存器5
        sd t6, 280(a0)   # 临时寄存器6
        
        # 恢复内核页表指针到t0
        ld t0, 0(a0)
        # 恢复内核栈指针
        ld sp, 8(a0)
        # 恢复usertrap的地址到t1
        ld t1, 16(a0)
        # 恢复hartid到tp
        ld tp, 32(a0)
        # 刷新之前的所有内存操作
        sfence.vma zero, zero
        # 切换到内核页表
        csrw satp, t0
        # 清空TLB
        sfence.vma zero, zero
        #跳转到usertrap()，处理来自用户空间的陷入
        jr t1

# 从内核态返回用户态
.global userret
userret:
        # 切换到用户页表，由usertrapret调用，将进程页表地址作为第一个参数传递，因此在a0寄存器中
        # 先切换到进程页表，因为以下寄存器加载到内存的时候都是在进程空间下进行的
        # 因此恢复也需要在进程空间下恢复，与陷入时的处理顺序正好相反
        sfence.vma zero, zero
        csrw satp, a0
        sfence.vma zero, zero
        # 加载trapframe基址，用于恢复寄存器

        li a0, TRAPFRAME
        
        ld ra, 40(a0)
        ld sp, 48(a0)
        ld gp, 56(a0)
        ld tp, 64(a0)
        ld t0, 72(a0)
        ld t1, 80(a0)
        ld t2, 88(a0)
        ld s0, 96(a0)
        ld s1, 104(a0)
        ld a1, 120(a0)
        ld a2, 128(a0)
        ld a3, 136(a0)
        ld a4, 144(a0)
        ld a5, 152(a0)
        ld a6, 160(a0)
        ld a7, 168(a0)
        ld s2, 176(a0)
        ld s3, 184(a0)
        ld s4, 192(a0)
        ld s5, 200(a0)
        ld s6, 208(a0)
        ld s7, 216(a0)
        ld s8, 224(a0)
        ld s9, 232(a0)
        ld s10, 240(a0)
        ld s11, 248(a0)
        ld t3, 256(a0)
        ld t4, 264(a0)
        ld t5, 272(a0)
        ld t6, 280(a0)

        ld a0, 112(a0)

        # 返回用户模式，sstatus和sepc寄存器的值已经在前边的代码中正确设置
        sret






